﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Windows;

namespace ElencySolutions.ImageMap
{
    internal static class ImageMapHelper
    {

        public static Dictionary<int, ShapeAndPoints> LoadAreasXml(string xml)
        {
            Areas areas = DeserializeObject<Areas>(xml);
            Dictionary<int, ShapeAndPoints> shapeAndPoints = new Dictionary<int, ShapeAndPoints>();

            foreach (Area area in areas)
            {
                ShapeAndPoints shapeAndPoint = new ShapeAndPoints();

                shapeAndPoint.AltText = area.AltText;
                shapeAndPoint.Target = area.Target == "_self" ? "Same window" : "New window";
                shapeAndPoint.Url = area.Url;

                switch (area.ShapeType)
                {
                    case "rect":
                        shapeAndPoint.ShapeType = ShapeType.Rectangle;
                        break;
                    case "circle":
                        shapeAndPoint.ShapeType = ShapeType.Circle;
                        break;
                    case "poly":
                        shapeAndPoint.ShapeType = ShapeType.Polygon;
                        break;
                }

                switch (shapeAndPoint.ShapeType)
                {
                    case ShapeType.Rectangle:
                        shapeAndPoint.Points.Add(new Point(area.Coordinates[0], area.Coordinates[1]));
                        shapeAndPoint.Width = area.Coordinates[2] - shapeAndPoint.Points[0].X;
                        shapeAndPoint.Height = area.Coordinates[3] - shapeAndPoint.Points[0].Y;
                        break;
                    case ShapeType.Circle:
                        int radius = area.Coordinates[2];
                        shapeAndPoint.Points.Add(new Point(area.Coordinates[0] - radius, area.Coordinates[1] - radius));
                        shapeAndPoint.Width = radius * 2;
                        shapeAndPoint.Height = radius * 2;
                        break;
                    case ShapeType.Polygon:
                        for (int i = 0; i < area.Coordinates.Count; i++)
                        {
                            shapeAndPoint.Points.Add(new Point(area.Coordinates[i], area.Coordinates[i + 1]));
                            i++;
                        }
                        break;
                }

                shapeAndPoints.Add(shapeAndPoints.Count, shapeAndPoint);
            }

            return shapeAndPoints;
        }

        public static Areas GetAreas(Dictionary<int, ShapeAndPoints> regions)
        {
            Areas areas = new Areas();

            foreach (KeyValuePair<int, ShapeAndPoints> item in regions)
            {
                ShapeAndPoints shapeAndPoints = item.Value;
                Area area = new Area
                {
                    Url = shapeAndPoints.Url,
                    AltText = shapeAndPoints.AltText,
                    Target = shapeAndPoints.Target == "Same window" ? "_self" : "_blank"
                };

                IEnumerable<int> coordinates = GetCoordinates(shapeAndPoints);

                switch (shapeAndPoints.ShapeType)
                {
                    case ShapeType.Rectangle:
                        area.ShapeType = "rect";
                        break;
                    case ShapeType.Circle:
                        area.ShapeType = "circle";
                        break;
                    case ShapeType.Polygon:
                        area.ShapeType = "poly";
                        break;
                }

                area.Coordinates.AddRange(coordinates);
                areas.Add(area);
            }

            return areas;
        }

        public static string GetAreaXml(Dictionary<int, ShapeAndPoints> regions)
        {
            return SerializeObject(GetAreas(regions));
        }

        private static IEnumerable<int> GetCoordinates(ShapeAndPoints shapeAndPoints)
        {
            List<int> coordinates = new List<int>();

            switch (shapeAndPoints.ShapeType)
            {
                case ShapeType.Rectangle:
                    Point point = shapeAndPoints.Points[0];
                    coordinates.Add((int)Math.Ceiling(point.X));
                    coordinates.Add((int)Math.Ceiling(point.Y));
                    coordinates.Add((int)Math.Ceiling(point.X) + (int)Math.Ceiling(shapeAndPoints.Width));
                    coordinates.Add((int)Math.Ceiling(point.Y) + (int)Math.Ceiling(shapeAndPoints.Height));
                    break;
                case ShapeType.Circle:
                    coordinates.Add((int)Math.Ceiling(shapeAndPoints.Points[0].X) + (int)Math.Ceiling(shapeAndPoints.Width / 2));
                    coordinates.Add((int)Math.Ceiling(shapeAndPoints.Points[0].Y) + (int)Math.Ceiling(shapeAndPoints.Height / 2));
                    coordinates.Add((int)Math.Ceiling(shapeAndPoints.Height / 2));
                    break;
                case ShapeType.Polygon:

                    foreach (Point currentPoint in shapeAndPoints.Points)
                    {
                        coordinates.Add((int)Math.Ceiling(currentPoint.X));
                        coordinates.Add((int)Math.Ceiling(currentPoint.Y));
                    }
                    break;
            }

            return coordinates;
        }

        #region Serialization methods

        /// <summary>
        /// Serialize an object into an XML string.
        /// </summary>
        /// <typeparam name="TSerializationType">The type of the serialization type.</typeparam>
        /// <param name="toSerialize">The object to serialize.</param>
        /// <returns>
        /// The object as XML.
        /// </returns>
        public static string SerializeObject<TSerializationType>(TSerializationType toSerialize)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(TSerializationType));

            using (MemoryStream memoryStream = new MemoryStream())
            {
                serializer.WriteObject(memoryStream, toSerialize);
                return Utf8ByteArrayToString(memoryStream.ToArray());
            }
        }

        /// <summary>
        /// Deserialize an object from an XML string.
        /// </summary>
        /// <param name="xml">The XML to deserialize.</param>
        /// <returns>The deserialized object.</returns>
        internal static TSerializationType DeserializeObject<TSerializationType>(string xml) where TSerializationType : new()
        {
            try
            {
                if (string.IsNullOrEmpty(xml))
                    return new TSerializationType();

                DataContractSerializer serializer = new DataContractSerializer(typeof(TSerializationType));

                using (MemoryStream memoryStream = new MemoryStream(StringToUtf8ByteArray(xml)))
                {
                    return (TSerializationType)serializer.ReadObject(memoryStream);
                }
            }
            catch
            {
                return new TSerializationType();
            }
        }

        /// <summary>
        /// Converts a byte array of Unicode values (UTF-8 encoded) into a string.
        /// </summary>
        /// <param name="characters">Unicode byte array to be converted.</param>
        /// <returns>A string converted from the Unicode byte array.</returns>
        private static string Utf8ByteArrayToString(byte[] characters)
        {
            return new UTF8Encoding().GetString(characters, 0, characters.Length);
        }

        /// <summary>
        /// Converts a string into a byte array of Unicode values (UTF-8 encoded).
        /// </summary>
        /// <param name="toConvert">The string to be converted.</param>
        /// <returns>A byte array of characters from the string.</returns>
        private static byte[] StringToUtf8ByteArray(string toConvert)
        {
            return new UTF8Encoding().GetBytes(toConvert);
        }

        #endregion Serialization methods


    }
}
